package com.lps.util.http;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpStatus;
import org.apache.http.ParseException;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.ContentBody;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;

import java.io.IOException;
import java.net.URI;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * utils for {@link org.apache.http.client.HttpClient}
 *
 * @author liangquanyi
 * @version 1.0
 * @since 2021/11/30 15:03
 */
public class HttpClientUtils {

    /*=============================== default charset ==================================*/
    /**
     * 默认的编码格式
     */
    public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;

    /*=============================== default httpClient ==================================*/

    /**
     * HttpClient构造器, 使用连接池, 并且配置了一些默认的参数
     */
    public static final HttpClientBuilder HTTP_CLIENT_BUILDER = HttpClients.custom();

    static {
        /* 创建连接池 */
        PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
        // 最大50个连接
        connectionManager.setMaxTotal(50);
        // 每个路由默认多少个连接
        connectionManager.setDefaultMaxPerRoute(50);
        HTTP_CLIENT_BUILDER.setConnectionManager(connectionManager);

        /* 设置请求默认配置 */
        RequestConfig requestConfig = buildRequestConfig(5000, 3000, 5000);
        HTTP_CLIENT_BUILDER.setDefaultRequestConfig(requestConfig);

        /* 设置默认请求头 */
        List<Header> defaultHeaders = new ArrayList<>();
        defaultHeaders.add(new BasicHeader(HttpHeaders.CONTENT_ENCODING, DEFAULT_CHARSET.name()));
        defaultHeaders.add(new BasicHeader(HttpHeaders.ACCEPT_CHARSET, DEFAULT_CHARSET.name()));
        defaultHeaders.add(new BasicHeader(HttpHeaders.ACCEPT_ENCODING, DEFAULT_CHARSET.name()));
        HTTP_CLIENT_BUILDER.setDefaultHeaders(defaultHeaders);
    }

    /*=============================== Get Method ==================================*/

    /**
     * 简易Get方法请求 - 返回String
     * {@link HttpClientUtils#httpGet(URI, Map, RequestConfig, boolean, Function)}
     */
    @NonNull
    public static String getStringFromHttpGet(@NonNull final URI uri,
                                              @NonNull final Map<String, String> headers,
                                              @Nullable final RequestConfig requestConfig,
                                              final boolean ignoreBadResponseStatus)
            throws IllegalArgumentException, IllegalStateException, IOException {
        return httpGet(uri, headers, requestConfig, ignoreBadResponseStatus, HttpClientUtils::getHttpEntityString);
    }

    /**
     * 简易Get方法请求 - 返回byte[]
     * {@link HttpClientUtils#httpGet(URI, Map, RequestConfig, boolean, Function)}
     */
    @NonNull
    public static byte[] getByteArrayFromHttpGet(@NonNull final URI uri,
                                                 @NonNull final Map<String, String> headers,
                                                 @Nullable final RequestConfig requestConfig,
                                                 final boolean ignoreBadResponseStatus)
            throws IllegalArgumentException, IllegalStateException, IOException {
        return httpGet(uri, headers, requestConfig, ignoreBadResponseStatus, HttpClientUtils::getHttpEntityByteArray);
    }

    /**
     * 简易Get方法请求
     * Content-type默认为 text/html; charset=utf-8
     * {@link HttpClientUtils#http(HttpRequestBase, Map, RequestConfig, boolean, Function)}
     */
    @NonNull
    public static <R> R httpGet(@NonNull final URI uri,
                                @NonNull final Map<String, String> headers,
                                @Nullable final RequestConfig requestConfig,
                                final boolean ignoreBadResponseStatus,
                                @Nullable final Function<HttpEntity, R> responseEntityConverter)
            throws IllegalArgumentException, IllegalStateException, IOException {

        final HttpGet httpGet = new HttpGet(uri);
        httpGet.setHeader(
                HttpHeaders.CONTENT_TYPE,
                ContentType.TEXT_HTML.withCharset(DEFAULT_CHARSET).toString());
        return http(httpGet, headers, requestConfig, ignoreBadResponseStatus, responseEntityConverter);
    }

    /*=============================== RequestConfig ==================================*/

    /**
     * 构建 RequestConfig
     *
     * @param socketTimeout            milliseconds 读取超时: 从请求的网址处获得响应数据的时间间隔
     * @param connectTimeout           milliseconds 连接超时: 完成TCP 3次握手的上限时间
     * @param connectionRequestTimeout milliseconds 连接池超时: 从连接池里面获取connection的超时时间
     * @return 带超时设置的RequestConfig
     */
    @NonNull
    public static RequestConfig buildRequestConfig(final int socketTimeout,
                                                   final int connectTimeout,
                                                   final int connectionRequestTimeout) {
        return RequestConfig.custom()
                .setSocketTimeout(socketTimeout)
                .setConnectTimeout(connectTimeout)
                .setConnectionRequestTimeout(connectionRequestTimeout)
                .build();
    }

    /*=============================== Post Method ==================================*/

    /**
     * 简易Post方法请求 - 提交表单
     * Content-type默认为 application/x-www-form-urlencoded; charset=utf-8
     * {@link HttpClientUtils#httpPost(URI, Map, HttpEntity, RequestConfig, boolean, Function)}
     */
    @NonNull
    public static <R> R formHttpPost(@NonNull final URI uri,
                                     @NonNull final Map<String, String> headers,
                                     @NonNull final Map<String, String> formData,
                                     @Nullable final RequestConfig requestConfig,
                                     final boolean ignoreBadResponseStatus,
                                     @Nullable final Function<HttpEntity, R> responseEntityConverter)
            throws IllegalArgumentException, IllegalStateException, IOException {

        // set name value pairs
        final List<BasicNameValuePair> nameValuePairs = formData.entrySet().stream()
                .filter(entry -> StringUtils.isNotBlank(entry.getKey()))
                .map(entry -> new BasicNameValuePair(entry.getKey(), entry.getValue()))
                .collect(Collectors.toList());
        final HttpEntity httpEntity = new UrlEncodedFormEntity(nameValuePairs, DEFAULT_CHARSET);
        return httpPost(uri, headers, ContentType.APPLICATION_FORM_URLENCODED, httpEntity,
                requestConfig, ignoreBadResponseStatus, responseEntityConverter);
    }

    /**
     * 简易Post方法请求 - 提交json
     * Content-type默认为 application/json; charset=utf-8
     * {@link HttpClientUtils#httpPost(URI, Map, HttpEntity, RequestConfig, boolean, Function)}
     */
    @NonNull
    public static <R> R jsonHttpPost(@NonNull final URI uri,
                                     @NonNull final Map<String, String> headers,
                                     @NonNull final String json,
                                     @Nullable final RequestConfig requestConfig,
                                     final boolean ignoreBadResponseStatus,
                                     @Nullable final Function<HttpEntity, R> responseEntityConverter)
            throws IllegalArgumentException, IllegalStateException, IOException {

        // set xml
        final HttpEntity httpEntity = new StringEntity(json, DEFAULT_CHARSET);
        return httpPost(uri, headers, ContentType.APPLICATION_JSON, httpEntity,
                requestConfig, ignoreBadResponseStatus, responseEntityConverter);
    }

    /**
     * 简易Post方法请求 - 提交xml
     * Content-type默认为 application/xml; charset=utf-8
     * {@link HttpClientUtils#httpPost(URI, Map, HttpEntity, RequestConfig, boolean, Function)}
     */
    @NonNull
    public static <R> R xmlHttpPost(@NonNull final URI uri,
                                    @NonNull final Map<String, String> headers,
                                    @NonNull final String xml,
                                    @Nullable final RequestConfig requestConfig,
                                    final boolean ignoreBadResponseStatus,
                                    @Nullable final Function<HttpEntity, R> responseEntityConverter)
            throws IllegalArgumentException, IllegalStateException, IOException {

        // set xml
        final HttpEntity httpEntity = new StringEntity(xml, DEFAULT_CHARSET);
        return httpPost(uri, headers, ContentType.APPLICATION_XML, httpEntity,
                requestConfig, ignoreBadResponseStatus, responseEntityConverter);
    }

    /**
     * 简易Post方法请求 - 提交binary和text
     * Content-type默认为 multipart/form-data; charset=utf-8
     * @param contentBodyParams 输入流数据/表单数据
     *                          ByteArrayBody ByteArray数据
     *                          FileBody File数据
     *                          InputStreamBody InputStream数据
     *                          StringBody String数据
     * {@link HttpClientUtils#httpPost(URI, Map, HttpEntity, RequestConfig, boolean, Function)}
     */
    @NonNull
    public static <R> R multipartHttpPost(@NonNull final URI uri,
                                          @NonNull final Map<String, String> headers,
                                          @NonNull final Map<String, ContentBody> contentBodyParams,
                                          @Nullable final RequestConfig requestConfig,
                                          final boolean ignoreBadResponseStatus,
                                          @Nullable final Function<HttpEntity, R> responseEntityConverter)
            throws IllegalArgumentException, IllegalStateException, IOException {

        final MultipartEntityBuilder builder = MultipartEntityBuilder.create()
                .setContentType(ContentType.MULTIPART_FORM_DATA.withCharset(DEFAULT_CHARSET))
                .setCharset(DEFAULT_CHARSET)
                .setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
        // set multipart file and form data
        contentBodyParams.forEach(builder::addPart);

        final HttpEntity httpEntity = builder.build();

        return httpPost(uri, headers, ContentType.MULTIPART_FORM_DATA, httpEntity,
                requestConfig, ignoreBadResponseStatus, responseEntityConverter);
    }

    /**
     * 简易Post方法请求
     * Content-type默认为 application/json; charset=utf-8 如需自定义, 可在headers中定义
     *
     * @param headerHttpEntity request的HttpEntity
     *                         设置request传递参数的数据和形式
     *                         {@link HttpClientUtils#http(HttpRequestBase, Map, RequestConfig, boolean, Function)}
     */
    @NonNull
    public static <R> R httpPost(@NonNull final URI uri,
                                 @NonNull final Map<String, String> headers,
                                 @Nullable final HttpEntity headerHttpEntity,
                                 @Nullable final RequestConfig requestConfig,
                                 final boolean ignoreBadResponseStatus,
                                 @Nullable final Function<HttpEntity, R> responseEntityConverter)
            throws IllegalArgumentException, IllegalStateException, IOException {

        return httpPost(uri, headers, ContentType.APPLICATION_JSON, headerHttpEntity,
                requestConfig, ignoreBadResponseStatus, responseEntityConverter);
    }

    /**
     * 简易Post方法请求
     * Content-type 需要自行配置在headers中
     *
     * @param headerHttpEntity request的HttpEntity
     *                         设置request传递参数的数据和形式
     * @param contentType      ContentType, 并设置UTF-8格式
     *                         {@link HttpClientUtils#http(HttpRequestBase, Map, RequestConfig, boolean, Function)}
     */
    @NonNull
    private static <R> R httpPost(@NonNull final URI uri,
                                  @NonNull final Map<String, String> headers,
                                  @NonNull ContentType contentType,
                                  @Nullable final HttpEntity headerHttpEntity,
                                  @Nullable final RequestConfig requestConfig,
                                  final boolean ignoreBadResponseStatus,
                                  @Nullable final Function<HttpEntity, R> responseEntityConverter)
            throws IllegalArgumentException, IllegalStateException, IOException {

        final HttpPost httpPost = new HttpPost(uri);
        if (null != headerHttpEntity) {
            httpPost.setEntity(headerHttpEntity);
        }
        httpPost.setHeader(HttpHeaders.CONTENT_TYPE,
                contentType.withCharset(DEFAULT_CHARSET).toString());

        return http(httpPost, headers, requestConfig, ignoreBadResponseStatus, responseEntityConverter);
    }

    /*=============================== HttpRequestBase ==================================*/

    /**
     * 简易Post方法请求
     *
     * @param httpRequest             请求体
     * @param headers                 请求头键值对, 已为Content-Encoding、Accept-Charset、Accept-Encoding配置默认值
     * @param requestConfig           RequestConfig
     * @param ignoreBadResponseStatus 是否忽略response status不为200的错误
     * @param responseEntityConverter 定义HttpEntity返回格式
     *                                如不转换, 则函数结束后response自动关闭, 导致HttpEntity返回流为空
     * @throws IllegalArgumentException 请求头参数设置出错
     * @throws IllegalStateException    返回状态不为200 且 ignoreBadResponseStatus为false
     * @throws IOException              连接失败
     */
    private static <R> R http(@NonNull final HttpRequestBase httpRequest,
                              @NonNull final Map<String, String> headers,
                              @Nullable final RequestConfig requestConfig,
                              final boolean ignoreBadResponseStatus,
                              @Nullable final Function<HttpEntity, R> responseEntityConverter)
            throws IllegalArgumentException, IllegalStateException, IOException {

        // set header
        headers.forEach(httpRequest::setHeader);

        // set requestConfig
        if (null != requestConfig) {
            httpRequest.setConfig(requestConfig);
        }

        // do connection
        // httpClient交由线程池管理 不需要手动关闭
        CloseableHttpClient httpClient = HTTP_CLIENT_BUILDER.build();
        try (CloseableHttpResponse response = httpClient.execute(httpRequest)) {
            // check the response statusCode
            final int statusCode = response.getStatusLine().getStatusCode();
            if ((statusCode == HttpStatus.SC_OK || ignoreBadResponseStatus)
                    && null != responseEntityConverter) {

                // get result data
                final HttpEntity httpEntity = response.getEntity();
                final R resData = responseEntityConverter.apply(httpEntity);

                // close the httpEntity
                EntityUtils.consume(httpEntity);

                return resData;
            } else {
                throw new IllegalStateException(String.format("response status code is not OK (%d)", statusCode));
            }
        } catch (ClientProtocolException e) {
            throw new IllegalArgumentException("have an error in the HTTP protocol!", e);
        }
    }

    /*=============================== HttpEntity ==================================*/

    /**
     * HttpEntity 转 byte[]
     *
     * @param httpEntity HttpEntity
     * @return 转换失败返回[]
     */
    @NonNull
    public static byte[] getHttpEntityByteArray(@NonNull final HttpEntity httpEntity) {
        try {
            return ArrayUtils.nullToEmpty(EntityUtils.toByteArray(httpEntity));
        } catch (IOException e) {
            return ArrayUtils.EMPTY_BYTE_ARRAY;
        }
    }

    /**
     * HttpEntity 转 String
     *
     * @param httpEntity HttpEntity
     */
    @NonNull
    public static String getHttpEntityString(@NonNull final HttpEntity httpEntity) {
        return getHttpEntityString(httpEntity, DEFAULT_CHARSET);
    }

    /**
     * HttpEntity 转 String
     *
     * @param httpEntity HttpEntity
     * @param charset    编码格式
     * @return 转换失败返回""
     */
    @NonNull
    public static String getHttpEntityString(@NonNull final HttpEntity httpEntity,
                                             @NonNull final Charset charset) {
        try {
            return StringUtils.defaultString(EntityUtils.toString(httpEntity, charset));
        } catch (IOException | ParseException e) {
            return "";
        }
    }

    private HttpClientUtils() {
    }
}
